﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.ObjectModel;

using XmlVisualizer.Models;
using XmlVisualizer.Exceptions;
using XmlVisualizer.Dialogs;

namespace XmlVisualizer.Controls {
	//public class MyXAttribute : INotifyPropertyChanged {
	//    public MyXAttribute(XAttribute attribute) {
	//        this.attribute = attribute;
	//        this.attribute.Changed += new EventHandler<XObjectChangeEventArgs>(attribute_Changed);
	//    }

	//    void attribute_Changed(object sender, XObjectChangeEventArgs e) {
	//        NotifyPropertyChanged("value");
	//    }

	//    private XAttribute attribute;

	//    public XAttribute Attribute {
	//        get { return attribute; }
	//    }

	//    public event PropertyChangedEventHandler PropertyChanged;

	//    private void NotifyPropertyChanged(String info) {
	//        if (PropertyChanged != null) {
	//            PropertyChanged(this, new PropertyChangedEventArgs(info));
	//        }
	//    }

	//    public void SetValue(String value) {
	//        if (value != attribute.Value) {
	//            attribute.SetValue(value);
	//            NotifyPropertyChanged("value");
	//        }
	//    }
	//}

	/// <summary>
	/// This user control is visual representation of XElement.
	/// </summary>
	public partial class ucElement : UserControl {
		#region Constructors
		/// <summary>
		/// Initializes the ucElement class.
		/// </summary>
		static ucElement() {
			FrameworkPropertyMetadata elementMetadata = new FrameworkPropertyMetadata(null,
				FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
			ElementProperty = DependencyProperty.Register("Element", typeof(XElement), typeof(ucElement), elementMetadata);
			
			FrameworkPropertyMetadata elementNameMetadata = new FrameworkPropertyMetadata(String.Empty,
				FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnElementNameChanged);
			ElementNameProperty = DependencyProperty.Register("ElementName", typeof(String), typeof(ucElement), elementNameMetadata);

			FrameworkPropertyMetadata elementNamespaceMetadata = new FrameworkPropertyMetadata(String.Empty,
				FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnElementNamespaceChanged);
			ElementNamespaceProperty = DependencyProperty.Register("ElementNamespace", typeof(String), typeof(ucElement), elementNamespaceMetadata);

			FrameworkPropertyMetadata namespacePrefixMetadata = new FrameworkPropertyMetadata(String.Empty,
				FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnNamespacePrefixChanged);
			NamespacePrefixProperty = DependencyProperty.Register("NamespacePrefix", typeof(String), typeof(ucElement), namespacePrefixMetadata);

			FrameworkPropertyMetadata observableAttributesMetadata = new FrameworkPropertyMetadata(null,
				FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
			ObservableAttributesProperty = DependencyProperty.Register("ObservableAttributes", typeof(AttributesList), typeof(ucElement), observableAttributesMetadata);
		}

		/// <summary>
		/// Initializes a new instance of the ucElement class.
		/// </summary>
		public ucElement() {
			InitializeComponent();
		}	

		/// <summary>
		/// Initializes a new instance of the ucElement class, setting the ucElement dependency properties ElementProperty and AttributesProperty.
		/// </summary>
		/// <param name="element">Reference to XElement.</param>
		/// <exception cref="System.ArgumentNullException">element is null.</exception>
		public ucElement(XElement element)
			: this() {
			if (element != null) {
				this.Element = element;
				this.ObservableAttributes = new AttributesList();
				this.ObservableAttributes.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ObservableAttributes_CollectionChanged);
				//this.ObservableAttributes.ContentChanged += new Action<object, EventArgs>(ObservableAttributes_ContentChanged);

				ucElement.FillObservableCollection<XAttribute>(ObservableAttributes,Element.Attributes());

				this.ElementName = element.Name.LocalName;
				this.ElementNamespace = element.Name.NamespaceName;
				this.NamespacePrefix = element.GetPrefixOfNamespace(element.Name.Namespace);
			} else {
				throw new ArgumentNullException("element", "element parameter can not be null");
			}
		}	
		#endregion

		#region Helper methods
		//public static IEnumerable<MyXAttribute> CreateAttList(IEnumerable<XAttribute> list) {
		//    List<MyXAttribute> attributes = new List<MyXAttribute>();
		//    foreach (XAttribute att in list) {
		//        attributes.Add(new MyXAttribute(att));
		//    }
		//    return attributes;
		//}

		protected Document GetParent() {
			DependencyObject obj = VisualTreeHelper.GetParent(this);
			if (obj == null)
				return null;
			while (!(obj is Document)) {
				obj = VisualTreeHelper.GetParent(obj);
				if (obj == null)
					return null;
			}
			return (Document)obj;
		}
		#endregion	

		#region Dependency properties
		/// <summary>
		/// Identifies the XmlVisualizer.Controls.ucElement.ElementNameProperty.
		/// </summary>
		public static DependencyProperty ElementNameProperty;

		/// <summary>
		/// Gets or set the ElementName property.
		/// </summary>
		public String ElementName {
			get { return (String)GetValue(ElementNameProperty); }
			set { SetValue(ElementNameProperty, value); }
		}

		/// <summary>
		/// Identifies the XmlVisualizer.Controls.ucElement.ElementNamespaceProperty.
		/// </summary>
		public static DependencyProperty ElementNamespaceProperty;
		
		/// <summary>
		/// Gets or sets the ElementNamespace property.
		/// </summary>
		public String ElementNamespace {
			get { return (String)GetValue(ElementNamespaceProperty); }
			set { SetValue(ElementNamespaceProperty, value); }
		}

		public static DependencyProperty NamespacePrefixProperty;

		public String NamespacePrefix {
			get { return (String)GetValue(NamespacePrefixProperty); }
			set { SetValue(NamespacePrefixProperty, value); }
		}

		/// <summary>
		/// Gets or sets the underlying XElement.
		/// </summary>
		public XElement Element {
			get { return (XElement)GetValue(ElementProperty); }
			set { SetValue(ElementProperty, value); }
		}

		/// <summary>
		/// Identifies the XmlVisualizer.Controls.ucElement.ElementProperty.
		/// </summary>
		public static DependencyProperty ElementProperty;

		/// <summary>
		/// Gets or sets the ObservableAttributesProperty.
		/// </summary>
		public AttributesList ObservableAttributes {
			get { return (AttributesList)GetValue(ObservableAttributesProperty); }
			set { SetValue(ObservableAttributesProperty, value); }
		}

		/// <summary>
		/// Identifies the XmlVisualizer.Controls.ucElement.ObservableAttributesProperty.
		/// </summary>
		public static DependencyProperty ObservableAttributesProperty;
		#endregion

		#region Event handlers
		void ObservableAttributes_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) {
			if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add) {
				IList newItems = e.NewItems;
				foreach (XAttribute att in newItems) {
					if (Element.Attribute(att.Name) == null) {
						Element.SetAttributeValue(att.Name, att.Value);
					}
				}
			} else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove) {
				IList removedItems = e.OldItems;
				foreach (XAttribute att in removedItems) {
					Element.SetAttributeValue(att.Name, null);
				}
			}
			
			//if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add) {
			//    IList newItems = e.NewItems;
			//    //foreach (MyXAttribute att in newItems) {
			//    //    if (Element.Attribute(att.Attribute.Name) == null) {
			//    //        //Element.SetAttributeValue(att.Name, att.Value);
			//    //        ParentDocument.Controller.UpdateAttributes(Element);
			//    //    }
			//    //}
			//} else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove) {
			//    IList removedItems = e.OldItems;
			//    //foreach (MyXAttribute att in removedItems) {
			//    //    //Element.SetAttributeValue(att.Name, null);
			//    //    ParentDocument.Controller.UpdateAttributes(Element);
			//    //}
			//}
		}

		/// <summary>
		/// Callback for PropertyChangedCallback in FrameworkPropertyMetadata class.
		/// </summary>
		/// <param name="d">The DependencyObject on which the property has changed value.</param>
		/// <param name="e">Event data that is issued by any event that tracks changes to the effective value of this property.</param>
		private static void OnElementNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
			if (d is ucElement) {
				ucElement element = d as ucElement;
				if (e.NewValue is String) {
					if (element.Element.Name.LocalName != (String)e.NewValue) {
						Document.CurrentController.SetElementName(element.Element, e.NewValue as String);
					}
				}
			}
		}

		/// <summary>
		/// Callback for PropertyChangedCallback in FrameworkPropertyMetadata class.
		/// </summary>
		/// <param name="d">The DependencyObject on which the property has changed value.</param>
		/// <param name="e">Event data that is issued by any event that tracks changes to the effective value of this property.</param>
		private static void OnElementNamespaceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
			if (d is ucElement) {
				ucElement element = d as ucElement;
				if (e.NewValue is String) {
					String newValue = e.NewValue as String;
					if (newValue != element.Element.Name.NamespaceName) {
						Document.CurrentController.SetElementNamespace(element.Element, newValue);
					}
				}
			}
		}

		private static void OnNamespacePrefixChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
			if (d is ucElement) {
				ucElement element = d as ucElement;
				if (e.NewValue is String) {
					Document.CurrentController.SetNamespacePrefix(element.Element, e.NewValue as String, element.Element.Name.Namespace);
				}
			}
		}

		/// <summary>
		/// Invoked when users clicks on attributes list view.
		/// </summary>
		/// <param name="sender">The object where the event handler is attached.</param>
		/// <param name="e">The event data.</param>
		private void lwAttributes_Click(object sender, RoutedEventArgs e) {
			if (e.OriginalSource is Button) {
				Button btn = e.OriginalSource as Button;
				XName tag = btn.Tag as XName;
				Element.SetAttributeValue(tag, null);
				ucElement.FillObservableCollection<XAttribute>(this.ObservableAttributes, Element.Attributes());
			}
		}
		#endregion

		#region Static methods
		/// <summary>
		/// Changes namespace of an element.
		/// </summary>
		/// <param name="element">XElement which namespace to change.</param>
		/// <param name="newNamespace">New namespace of element.</param>
		public static void ChangeNamespace(XElement element, XNamespace newNamespace) {
			try {
				element.SetAttributeValue("xmlns", null);
				element.Name = newNamespace.GetName(element.Name.LocalName);
				element.SetAttributeValue("xmlns", newNamespace.NamespaceName);
			} catch (ArgumentException ex) {
				ExceptionHandling.HandleException(ex);
			} catch (XmlException ex) {
				ExceptionHandling.HandleException(ex);
			} catch (Exception ex) {
				ExceptionHandling.HandleException(ex);
			}

		}

		/// <summary>
		/// Fills observable collection with items from collection.
		/// </summary>
		/// <typeparam name="T">any class</typeparam>
		/// <param name="observableCollection">observable collection of elements of type T</param>
		/// <param name="collection">any collection that implements IEnumerable<typeparamref name="T"/></param>
		/// <exception cref="System.ArgumentNullException">observableCollection or collection is null.</exception>
		public static void FillObservableCollection<T>(ObservableCollection<T> observableCollection, IEnumerable<T> collection) where T : class {
			if (observableCollection == null)
				throw new ArgumentNullException("observableCollection");
			if (collection == null)
				throw new ArgumentNullException("collection");

			observableCollection.Clear();
			foreach (T item in collection) {
				observableCollection.Add(item);
			}
		}
		#endregion

		#region Methods
		/// <summary>
		/// Sets the value of an attribute, adds an attribute, or removes an attribute.
		/// </summary>
		/// <param name="name">An <see cref="XName">XName</see> that contains the name of the attribute to change.</param>
		/// <param name="value">The value to assign to the attribute. The attribute is removed if the value is null.
		/// Otherwise, the value is converted to its string representation and assigned to the Value property of the attribute.</param>
		public void SetAttributeValue(XName name, object value) {
			try {
				Element.SetAttributeValue(name, value);
				FillObservableCollection<XAttribute>(ObservableAttributes, Element.Attributes());
			} catch (ArgumentException ex) {
				ExceptionHandling.HandleException(ex);
			}
		}
		#endregion		
	}

	/// <summary>
	/// Converts IEnumerable&lt;XAttribute&gt; to Visibility structure.
	/// </summary>
	public class VisibilityConverter : IValueConverter {
		/// <summary>
		/// Converts IEnumerable&lt;XAttribute&gt; to Visibility structure.
		/// </summary>
		/// <param name="value">object to convert</param>
		/// <param name="targetType">target type</param>
		/// <param name="parameter">conversion's parameter</param>
		/// <param name="culture">CultureInfo</param>
		/// <returns>Visibility.Visible if the list with XAttributes is not empty, otherwise Visibility.Collapsed.</returns>
		public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
			if (value is IEnumerable<XAttribute>) {
				IEnumerable<XAttribute> attributes = value as IEnumerable<XAttribute>;
				if (attributes.Count() > 0) {
					return Visibility.Visible;
				} else {
					return Visibility.Collapsed;
				}
			} else {
				return Visibility.Collapsed;
			}
		}

		/// <summary>
		/// Returns null.
		/// </summary>
		/// <param name="value"></param>
		/// <param name="targetType"></param>
		/// <param name="parameter"></param>
		/// <param name="culture"></param>
		/// <returns>null.</returns>
		public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
			return null;
		}
	}
}
